﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using ERF;
using ERF.Physics;
using FirstPerson.Objects;
using FirstPerson.Objects.Weapons;
using FirstPerson.Objects.Pickups;
using System.Threading;
using FirstPerson.Gametypes;
using ERF.Menu;

namespace FirstPerson.States
{
    class PlayingState : State
    {
        /// <summary>
        /// Singleton instance
        /// </summary>
        private static PlayingState instance;

        private AutoResetEvent resetEvent;

        //private ScoreBoard scoreBoard;

        /// <summary>
        /// Get an instance of this state.
        /// </summary>
        /// <param name="machine">the state machine that this state should belong to.</param>
        /// <returns></returns>
        public static PlayingState GetInstance(StateMachine machine)
        {
            if (instance == null)
                instance = new PlayingState(machine);

            return instance;
        }

        private PhysicsEngine physicsEngine;
        private SceneGraph sceneGraph;
        public Particle3DSample.ParticleSystem particleSystem;

        public Particle3DSample.ParticleSystem particleSystemGhost;
        public Particle3DSample.ParticleSystem particleSystemGhostAngry;

        private GameWorld gameWorld;
        private Gametypes.GameType gameType;
        private Map map;
        private Hud hud;
        private GameData gameData;

        public GameData GameData
        {
            get { return gameData; }
        }

        public PhysicsEngine PhysicsEngine
        {
            get { return physicsEngine; }
            set { physicsEngine = value; }
        }

        public Map Map
        {
            get { return map; }
        }

        public Gametypes.GameType GameType
        {
            get { return gameType; }
        }

        public GameWorld GameWorld
        {
            get { return gameWorld; }
            set { gameWorld = value; }
        }

        public Hud Hud
        {
            get { return hud; }
            set { hud = value; }
        }

        private PlayingState(StateMachine stateMachine)
            : base(stateMachine)
        {
              
        }

        /// <summary>
        /// Initialize PlayingState with Physics engine, SeceneGraph
        /// </summary>
        public override void Initialize()
        {
            this.InitializePhysics();
            this.InitializeCamera();
            machine.CurrentGame.NetworkHandler.GamerJoined += new EventHandler<GamerJoinedEventArgs>(NetworkHandler_GamerJoined);

            // Create scenegraph.
            sceneGraph = new SceneGraph(this.machine.CurrentGame);
            sceneGraph.UpdateEnd += new EventHandler(sceneGraph_UpdateEnd);

            base.Initialize(); //important as it sets the state to initialized!
        }

        public void initMatch(GameData matchData)
        {
            this.sceneGraph.Items.Clear();
            
            this.gameData = matchData;

            this.map = Map.Load(this.machine.CurrentGame, matchData.mapName);

            switch (matchData.gameTypeNumber)
            {
                case GameTypeNumber.DeathMatch:
                    DeathMatch dm = new DeathMatch(machine.CurrentGame);
                    dm.DurationTime = matchData.timeLimit;
                    dm.KillLimit = matchData.killLimit;

                    this.gameType = dm;
                    break;
                case GameTypeNumber.GhostMatch:
                    GhostMatch gm = new GhostMatch(machine.CurrentGame);
                    gm.DurationTime = matchData.timeLimit;
                    gm.KillLimit = matchData.killLimit;

                    this.gameType = gm;
                    break;
                default:
                    this.gameType = new DeathMatch(this.machine.CurrentGame);
                    break;
            }

            this.LoadWorld();
            this.physicsEngine.Scene = this.gameWorld;

            /*
             * Add some health packs and ammo
             */

            Stack<WayPoint> possibleWaypoints = new Stack<WayPoint>();
            int noWaypoints = 25;
            int noAmmos = 15;
            int noHps = 10;
            Random random = new Random(matchData.randomSeed);

            for (int i = 0; i < noWaypoints; i++)
            {
                WayPoint wp;
                do
                {
                    wp = map.WayPoints[(int)(random.NextDouble() * map.WayPoints.Length)];
                } while (possibleWaypoints.Contains(wp));

                possibleWaypoints.Push(wp);
            }

            // add health packs
            while (possibleWaypoints.Count > 0)
            {
                WayPoint wp1 = possibleWaypoints.Pop();

                Healthpack pg = new Healthpack(this.machine.CurrentGame);
                pg.Position = new Vector3(wp1.Position.X, 100, wp1.Position.Y);
                pg.PhysicsBody.position = pg.Position;
                this.physicsEngine.AddBody(pg.PhysicsBody);
                this.sceneGraph.Items.Add(pg);

                if (noWaypoints - possibleWaypoints.Count >= noHps)
                    break;
            }

            // add ammunition
            while (possibleWaypoints.Count > 0)
            {
                WayPoint wp1 = possibleWaypoints.Pop();

                Ammo pg = new Ammo(this.machine.CurrentGame);
                pg.Position = new Vector3(wp1.Position.X, 100, wp1.Position.Y);
                pg.PhysicsBody.position = pg.Position;
                this.physicsEngine.AddBody(pg.PhysicsBody);
                this.sceneGraph.Items.Add(pg);

                if (possibleWaypoints.Count >= noAmmos) //will not be reached because it uses the last waypoints.
                    break;
            }

            this.machine.CurrentGame.CurrentSceneGraph = sceneGraph;

            // Add HUD
            hud = new Hud(this.machine.CurrentGame);
            this.machine.CurrentGame.Components.Add(hud);

            //Add smoke particle system
            this.particleSystem = new Particle3DSample.ParticleSystem(this.machine.CurrentGame, this.machine.CurrentGame.Content, "ParticleData\\SmokePlumeSettings");
            this.sceneGraph.Items.Add(this.particleSystem);

            // add ghost system
            this.particleSystemGhost = new Particle3DSample.ParticleSystem(this.machine.CurrentGame, this.machine.CurrentGame.Content, "ParticleData\\GhostSettings");
            this.sceneGraph.Items.Add(this.particleSystemGhost);
            // add ghost system angry
            this.particleSystemGhostAngry = new Particle3DSample.ParticleSystem(this.machine.CurrentGame, this.machine.CurrentGame.Content, "ParticleData\\GhostSettingsAngry");
            this.sceneGraph.Items.Add(this.particleSystemGhostAngry);

            this.physicsEngine.Start();

            // Stop the menu background music.
            if (MediaPlayer.State == MediaState.Playing)
                MediaPlayer.Stop();

            // Add player
            HumanPlayer humanPlayer = ((FirstPerson.PlayerHandler)this.machine.CurrentGame.PlayerHandler).HumanPlayer;
            humanPlayer.Position = gameType.PickSpawnpointPosition();
            physicsEngine.AddBody(humanPlayer.PhysicsBody);
            this.machine.CurrentGame.CurrentSceneGraph.Items.Add(humanPlayer);

            gameType.init(TimeSpan.FromSeconds(matchData.startTime));
        }

        private bool GetPickupAtWayPoint(Pickable wayPoint, List<WayPoint> waypoints)
        {
            foreach (WayPoint wp in waypoints)
            {
                if (wp.Position.Equals(wp.Position))
                    return true;
            }

            return false;
        }

        void NetworkHandler_GamerJoined(object sender, GamerJoinedEventArgs e)
        {
            if (e.Gamer.IsLocal)
            {
                HumanPlayer player = (HumanPlayer)e.Gamer.Tag;
                player.PhysicsBody.BodyChanged += new EventHandler(PhysicsBody_BodyChanged);
            }
            else
            {
                Player player = (Player)e.Gamer.Tag;
                this.sceneGraph.Items.Add(player);
                this.physicsEngine.AddBody(player.PhysicsBody);
            }
        }

        void PhysicsBody_BodyChanged(object sender, EventArgs e)
        {
            Body body = sender as Body;
            this.gameWorld.SkyBox.Position = base.machine.CurrentGame.Camera.Position;
            //this.gameWorld.SkyBox.
        }

        void sceneGraph_UpdateEnd(object sender, EventArgs e)
        {
            if (this.physicsEngine.IsRunning)
            {
                this.resetEvent.Set();
                this.physicsEngine.physicsResetEvent.WaitOne();
            }
        }

        /// <summary>
        /// Initialize camera
        /// </summary>
        private void InitializeCamera()
        {
            this.machine.CurrentGame.Camera = new Camera(
                this.machine.CurrentGame,
                MathHelper.PiOver4,
                this.machine.CurrentGame.GraphicsDevice.Viewport.AspectRatio,
                0.01f,
                10000.0f,
                new Vector3(0, 10, 60),
                new Vector2(0, 0),
                Vector3.Up);
        }

        /// <summary>
        /// Load a map to make an gameWorld and set it in sceneGraph
        /// </summary>
        private void LoadWorld()
        {
            this.gameWorld = new GameWorld(base.machine.CurrentGame, this.map);
            this.sceneGraph.Items.Insert(0, this.gameWorld);
        }

        /// <summary>
        /// Initialize Physics engine
        /// </summary>
        private void InitializePhysics()
        {
            this.resetEvent = new AutoResetEvent(false);
            this.physicsEngine = new PhysicsEngine(this.machine.CurrentGame, this.resetEvent);
            Gravitylogic logic = new Gravitylogic(new Vector3(0, -9.82f, 0));
            this.physicsEngine.AddLogic(logic);
            this.physicsEngine.AddLogic(new DampingLogic(8f));
            this.physicsEngine.AddLogic(new DragLogic(16f));
            //this.physicsEngine.AddLogic(new DragLogic());

            //this.machine.CurrentGame.Components.Add(this.physicsEngine);
        }

        /// <summary>
        ///
        /// </summary>
        public override void Update(GameTime gameTime)
        {
            if (gameType.IsGameOver(gameTime))
            {
                this.hud.DeactivateHud();
                this.machine.CurrentGame.Window.Title = "Game OVER!";
                this.machine.CurrentState = EndState.GetInstance(machine);
                this.Hud.ShowScores(true);
                if (this.physicsEngine.IsRunning)
                    this.physicsEngine.Stop();
                //physicsEngine.Enabled = false;
                ((FirstPerson.PlayerHandler)this.machine.CurrentGame.PlayerHandler).HumanPlayer.Enabled = false;
            }


            if (input.IsButtonReleased("MENU_BUTTON"))
            {
                this.machine.CurrentState = PlayingMenuState.GetInstance(machine);
                MenuManager.GetInstance(machine.CurrentGame).Visible = true;
                ((FirstPerson.PlayerHandler)this.machine.CurrentGame.PlayerHandler).HumanPlayer.Enabled = false;
            }

            if (input.IsButtonDown("SHOW_SCORE", gameTime, false))
                this.Hud.ShowScores(true);
            else if (input.IsButtonReleased("SHOW_SCORE"))
                this.Hud.ShowScores(false);
        }

        public void EndMatch()
        {
            this.physicsEngine.RemoveAllBodies();
            (base.machine.CurrentGame.PlayerHandler as FirstPerson.PlayerHandler).PlayersList.Clear();

            this.gameWorld.Dispose();
            this.gameWorld = null;
            this.map = null;
        }

        public override void Continue()
        {
            ((FirstPerson.PlayerHandler)this.machine.CurrentGame.PlayerHandler).HumanPlayer.Enabled = true;
            MenuManager.GetInstance(machine.CurrentGame).Visible = false;
            
            base.Continue();
        }
    }
}
